/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package fhv.itb.mst2955.client.ui;

import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Hyperlink;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import fhv.itb.mst2955.client.AddressService;
import fhv.itb.mst2955.client.AddressServiceAsync;
import fhv.itb.mst2955.client.controllers.ContactController;
import fhv.itb.mst2955.client.event.UpdateEventListener;
import fhv.itb.mst2955.client.model.AddressProxy;
import fhv.itb.mst2955.client.model.ContactProxy;
import fhv.itb.mst2955.client.model.CountryProxy;
import java.util.Date;
import java.util.HashMap;


/**
 * The contact tab of the application.
 * It contains the contact list and provides forms for editing contact information.
 */
public class ContactTab extends VerticalPanel implements ClickHandler, UpdateEventListener {

    private final int LAST_NAME_INDEX = 0;
    private final int FIRST_NAME_INDEX = 1;
    private final int STREET_INDEX = 2;
    private final int COUNTRY_INDEX = 3;
    private final int ZIP_INDEX = 4;
    private final int CITY_INDEX = 5;
    private final int COMMAND_INDEX = 6;
    
    private ContactController contactController;

    /**
     * The contact which is currently opened for editing.
     */
    private ContactProxy currentContact;

    /**
     * The address which is currently opened for editing.
     */
    private AddressProxy currentAddress;

    private boolean contactModified;
    private boolean addressModified;

    
    // mapping of contact ID to position in table (HashMap<contactTable.row, contactID>)
    HashMap<Integer, Integer> idMap;

    FlexTable contactTable;
    FlexTable contactInfo;
    HorizontalPanel buttonsPanel;
    Button newContactButton;
    Button handleConflictsButton;


    @SuppressWarnings("static-access")
    public ContactTab() {
        super();

        contactModified = false;
        addressModified = false;

//        History.newItem("contacts");
        idMap = new HashMap<Integer, Integer>();
        contactController = ContactController.getInstance();
        contactController.addUpdateListener(this);

        buttonsPanel = new HorizontalPanel();
        add(buttonsPanel);

        contactTable = new FlexTable();
        add(contactTable);

        newContactButton = new Button("New contact", this);
        buttonsPanel.add(newContactButton);

        handleConflictsButton = new Button("Resolve conflicts!", this);
        handleConflictsButton.addStyleName("conflicts-Button");
        buttonsPanel.add(handleConflictsButton);

        contactController.init();
//        test();
    }


    /**
     * Builds the table of contacts from the list supplied by the controller.
     */
    private void buildContactsTable() {
        HashMap<Integer, CountryProxy> countries = contactController.getCountries();
        HashMap<Integer, AddressProxy> addresses = contactController.getAddresses();
        HashMap<Integer, ContactProxy> contacts = contactController.getContacts();

        if (contactTable != null && getChildren().contains(contactTable)) {
            remove(contactTable);
            contactTable = new FlexTable();
        }

        contactTable.setCellPadding(5);
        contactTable.addStyleName("FlexTable");

        int rows = 0;
        // header row
        contactTable.setText(rows, LAST_NAME_INDEX, "Last name");
        contactTable.setText(rows, FIRST_NAME_INDEX, "First name");
        contactTable.setText(rows, STREET_INDEX, "Street");
        contactTable.setText(rows, COUNTRY_INDEX, "Country");
        contactTable.setText(rows, ZIP_INDEX, "Zip Code");
        contactTable.setText(rows, CITY_INDEX, "City");
        contactTable.setText(rows, COMMAND_INDEX, "");
        contactTable.getRowFormatter().addStyleName(rows, "FlexTable-Header");

        rows = 1;
        for (ContactProxy c : contacts.values()) {
            AddressProxy ads = addresses.get(c.getAddressID());
            CountryProxy ctry = countries.get(ads.getCountryID());

            idMap.put(rows, c.getId());
            contactTable.insertRow(rows);
            String style = rows % 2 == 0 ? "FlexTable-EvenRow" : "FlexTable-OddRow";
            contactTable.getRowFormatter().addStyleName(rows, style);

            Hyperlink link = new Hyperlink(c.getLastName(), "contact_" + rows);
            link.addClickHandler(this);
            contactTable.setWidget(rows, LAST_NAME_INDEX, link);
            contactTable.setText(rows, FIRST_NAME_INDEX, c.getFirstName());
            contactTable.setText(rows, STREET_INDEX, ads.getStreet());
            contactTable.setText(rows, COUNTRY_INDEX, ctry.getNickName());
            contactTable.setText(rows, ZIP_INDEX, ads.getZip());
            contactTable.setText(rows, CITY_INDEX, ads.getCity());
            Button deleteButton = new Button("delete");
            deleteButton.addClickHandler(new DeleteButtonKlickHandler(rows));
            contactTable.setWidget(rows, COMMAND_INDEX, deleteButton);
            rows++;
        }
        add(contactTable);

        // deal with conflicts if necessary
        if (contactController.getSyncConflicts().size() > 0) {
            if (buttonsPanel.getWidgetCount() == 1) buttonsPanel.add(handleConflictsButton);
        } else if (buttonsPanel.getWidgetCount() == 2) buttonsPanel.remove(handleConflictsButton);
    }


    /**
     * Opens the form to edit the contact's data.
     *
     * @param contact
     * The contact to be edited.
     *
     * @param contactIndex
     * The index (row) of the contact in the table.
     */
    private void openContactInfo(ContactProxy contact, int contactIndex) {
        contactInfo = new FlexTable();
        currentContact = contact;

        contactInfo.setText(LAST_NAME_INDEX, 0, "Last name");
        TextBox lastName = new TextBox();
        lastName.setVisibleLength(40);
        contactInfo.setWidget(LAST_NAME_INDEX, 1, lastName);

        contactInfo.setText(FIRST_NAME_INDEX, 0, "First name");
        TextBox firstName = new TextBox();
        firstName.setVisibleLength(40);
        contactInfo.setWidget(FIRST_NAME_INDEX, 1, firstName);

        contactInfo.setText(STREET_INDEX, 0, "Street");
        TextBox street = new TextBox();
        street.setVisibleLength(40);
        contactInfo.setWidget(STREET_INDEX, 1, street);

        contactInfo.setText(COUNTRY_INDEX, 0, "Country");
        ListBox countryListBox = new ListBox();
        for (String nick : contactController.getCountryNickNames()) countryListBox.addItem(nick);
        contactInfo.setWidget(COUNTRY_INDEX, 1, countryListBox);

        contactInfo.setText(ZIP_INDEX, 0, "Zip Code");
        TextBox zip = new TextBox();
        zip.setVisibleLength(40);
        contactInfo.setWidget(ZIP_INDEX, 1, zip);

        contactInfo.setText(CITY_INDEX, 0, "City");
        TextBox city = new TextBox();
        city.setVisibleLength(40);
        contactInfo.setWidget(CITY_INDEX, 1, city);

        Hyperlink link = new Hyperlink("Save", "save_contact_" + contactIndex);
        link.addClickHandler(this);
        contactInfo.setWidget(COMMAND_INDEX, 0, link);

        lastName.setText(currentContact.getLastName());
        firstName.setText(currentContact.getFirstName());

        if (currentContact.getAddressID() != null) {
            currentAddress = contactController.getAddress(contact.getAddressID());

            if (currentAddress != null) {
                street.setText(currentAddress.getStreet());
                zip.setText(currentAddress.getZip());
                city.setText(currentAddress.getCity());

                // find index of country in list
                int index = 0;
                for (index = 0; !countryListBox.getItemText(index).equals(currentAddress.getCountryNickName()); index++);
                countryListBox.setSelectedIndex(index);
            }

        } else { // new contact
            currentAddress = new AddressProxy();
            currentAddress.setVersionNr(0);
            currentAddress.setCountryID(1);
            currentAddress.setCountry(contactController.getCountryByNickName("A"));
            countryListBox.setSelectedIndex(0);
            currentAddress.setId(contactController.getNextAddressId());
            currentContact.setAddressID(currentAddress.getId());
        }

        contactTable.insertRow(contactIndex + 1);
        contactTable.getFlexCellFormatter().setColSpan(contactIndex + 1, 0, 6);
        contactTable.setWidget(contactIndex + 1, 0, contactInfo);
    }


    /**
     * Validation of input for saving the contact's edited information.
     *
     * @return
     * True, if the validation was successful, false otherwise.
     */
    private boolean validateForSave() {
        int contactChanged = 0;
        int addressChanged = 0;

        // validate
        if (currentContact == null || currentAddress == null || contactInfo == null) {
            Window.alert("Client or Address information missing!");
            return false;
        }

        String input = getInput(LAST_NAME_INDEX); // last name
        if (input == null || input.equals("")) {
            Window.alert(("Please enter last name!"));
            return false;
        } else if (!input.equals(currentContact.getLastName())) {
            contactChanged++;
            currentContact.setLastName(input);
        }
        
        input = getInput(FIRST_NAME_INDEX); // first name
        if (input == null || input.equals("")) {
            Window.alert(("Please enter first name!"));
            return false;
        } else if (!input.equals(currentContact.getFirstName())) {
            contactChanged++;
            currentContact.setFirstName(input);
        }

        input = getInput(STREET_INDEX); // street
        if (input != null && !input.equals("") && !input.equals(currentAddress.getStreet())) {
            currentAddress.setStreet(input);
            addressChanged++;
        }

        ListBox clb = (ListBox)contactInfo.getWidget(COUNTRY_INDEX, 1);
        input = clb.getItemText(clb.getSelectedIndex()); // country
        if (input == null || input.equals("")) {
            Window.alert(("Please select a country!"));
            return false;
        } else if (!input.equals(currentAddress.getCountry().getNickName())) {
            CountryProxy c = contactController.getCountryByNickName(input);
            if (c != null) {
                addressChanged++;
                currentAddress.setCountry(c);
                currentAddress.setCountryID(c.getId());
            }
        }

        input = getInput(ZIP_INDEX); // zip code
        if (input == null || input.equals("")) {
            Window.alert(("Please enter zip code!"));
            return false;
        } else if (!input.equals(currentAddress.getZip())) {
            addressChanged++;
            currentAddress.setZip(input);
        }

        input = getInput(CITY_INDEX); // city
        if (input == null || input.equals("")) {
            Window.alert(("Please enter city!"));
            return false;
        } else if (!input.equals(currentAddress.getCity())) {
            addressChanged++;
            currentAddress.setCity(input);
        }

        if (addressChanged > 0) {
            currentAddress.setVersionNr(currentAddress.getVersionNr()+1);
            addressModified = true;
        }
        if (contactChanged > 0) {
            currentContact.setVersionNr(currentContact.getVersionNr()+1);
            contactModified = true;
        }

        return true;
    }


    // helper method for getting the text from the text box at the specified position.
    private String getInput(int position) {
        TextBox text = (TextBox) contactInfo.getWidget(position, 1);
        return text.getText();
    }


    /**
     * Opens the editing form for entering a new contact's information.
     */
    private void insertNewContact() {
        int rows = contactTable.getRowCount();

        // make sure no other dialog is open (or that the table is empty)
        if (currentContact == null && contactInfo == null &&
                (contactTable.getText(rows-1, 0) != null &&
                (!contactTable.getText(rows-1, 0).equals("") ||
                contactTable.getText(rows-1, 0).equals("Last name")))) {
            ContactProxy c = new ContactProxy();
            c.setId(contactController.getNextContactId());
            c.setVersionNr(0);

            contactTable.insertRow(rows);
            String style = rows % 2 == 0 ? "FlexTable-EvenRow" : "FlexTable-OddRow";
            contactTable.getRowFormatter().addStyleName(rows, style);


            Hyperlink link = new Hyperlink("", "contact_" + rows);
            link.addClickHandler(this);
            contactTable.setWidget(rows, LAST_NAME_INDEX, link);
            contactTable.setText(rows, FIRST_NAME_INDEX, "");
            contactTable.setText(rows, STREET_INDEX, "");
            contactTable.setText(rows, COUNTRY_INDEX, "A");
            contactTable.setText(rows, ZIP_INDEX, "");
            contactTable.setText(rows, CITY_INDEX, "");
            Button deleteButton = new Button("delete");
            deleteButton.addClickHandler(new DeleteButtonKlickHandler(rows));
            contactTable.setWidget(rows, COMMAND_INDEX, deleteButton);

            openContactInfo(c, rows);
        }


    }


    public void onUpdateDone() {
        // Update the table only if no changes are currently being made.
        // Otherwise ignore the update and wait for the next one.
        if (currentContact == null) buildContactsTable();
    }


    public void onClick(ClickEvent event) {
        if (event.getSource().equals(newContactButton)) {
            insertNewContact();
        } else if (event.getSource().equals(handleConflictsButton)) {
            contactController.handleConflicts();
        } else {
            // contact link clicked, get contact info
            String s = ((Hyperlink) event.getSource()).getTargetHistoryToken();

            if (s.startsWith("contact_")) {
                int contactIndex = Integer.valueOf(s.substring(8));
                ContactProxy contact = contactController.getContact(idMap.get(contactIndex));

                // check, if this contacts info is already open
                if (currentContact == contact) {
                    // close the open info
                    contactTable.removeRow(contactIndex + 1);
                    currentContact = null;
                    currentAddress = null;
                    contactInfo = null;
//                  History.newItem("contacts");
                } else if (currentContact != null) { // other contact info open
                    // find row for currently open contact
                    for (int row = 0; row < contactTable.getRowCount(); row++) {
                        if (contactTable.getWidget(row, 0) != null && contactTable.getWidget(row, 0).equals(contactInfo)) {
                            contactTable.removeRow(row);
                            if (contactTable.getText(row-1, 0) == null || contactTable.getText(row-1, 0).equals("")) {
                                contactTable.removeRow(row-1); // new contact dialog open
                            }
                        }
                    }

//                    for (int row = 0; row < idMap.size(); row++) {
//                        if (idMap.get(row) == currentClient.getId()) {
//                            contactIndex = row;
//                        }
//                    }

                    currentContact = null;
                    currentAddress = null;
                    contactInfo = null;
//                  History.newItem("contacts");
                    contactIndex = Integer.valueOf(s.substring(8));
                    openContactInfo(contact, contactIndex);
                } else {
                    openContactInfo(contact, contactIndex);
                }

            } else if (s.startsWith("save_contact_")) {
                int contactIndex = Integer.valueOf(s.substring(13));
                if (validateForSave() == true) {
                    // save
                    if (currentAddress.getVersionNr() == 1 && addressModified) {
                        contactController.putNewAddress(currentAddress);
                        addressModified = false;
                    } else if (addressModified) {
                        contactController.updateAddress(currentAddress);
                        addressModified = false;
                    }
                    if (currentContact.getVersionNr() == 1 && contactModified) {
                        contactController.putNewContact(currentContact);
                        contactModified = false;
                    } else if (contactModified) {
                        contactController.updateContact(currentContact);
                        contactModified = false;
                    }

                    if (!idMap.containsValue(currentContact.getId())) {
                        idMap.put(contactIndex, currentContact.getId());
                    }

                    // close the open info
                    contactTable.removeRow(contactIndex + 1);
                    currentContact = null;
                    currentAddress = null;
                    contactInfo = null;
                    buildContactsTable();
//                  History.newItem("contacts");
                }
            }
        }

    }


    /**
     * Handler for the delete buttons (to delete a contact).
     */
    private class DeleteButtonKlickHandler implements ClickHandler {

        /**
         * The index of the contact object in the contacts list.
         */
        int contactIndex;


        public DeleteButtonKlickHandler(int index) {
            contactIndex = index;
        }


        public void onClick(ClickEvent event) {
            contactController.deleteContact(idMap.get(contactIndex));
            idMap.remove(contactIndex);
        }
    }


    // TESTING
    private void test() {
        // testing
        Button insertRemoteButton = new Button("insert remote test");
        add(insertRemoteButton);

        insertRemoteButton.addClickHandler(new ClickHandler() {

            public void onClick(ClickEvent event) {
                AddressServiceAsync as = contactController.getAddressService();
                testCount = 0;

                // insert 100 contacts into remote db and record overall processing time
                start = new Date();

                for (int i = 0; i < 100; i++) {
                    AddressProxy a = new AddressProxy();
                    a.setCity("Test City");
                    a.setStreet("Test Street");
                    a.setCountryID(1);
                    a.setZip("0000");
                    a.setVersionNr(1);
                    a.setId(contactController.getNextAddressId());
                    as.newAddress(a, testCallback);
                }

            }
        });


        Button insertLocalButton = new Button("insert local test");
        add(insertLocalButton);

        insertLocalButton.addClickHandler(new ClickHandler() {

            public void onClick(ClickEvent event) {
                testCount = 0;

                // insert 100 contacts into local db and record overall processing time
                start = new Date();

                for (int i = 0; i < 100; i++) {
                    AddressProxy a = new AddressProxy();
                    a.setCity("Test City");
                    a.setStreet("Test Street");
                    a.setCountryID(1);
                    a.setZip("0000");
                    a.setVersionNr(1);
                    a.setId(contactController.getNextAddressId());
                    contactController.putNewAddress(a);
                }

                end = new Date();
                Long difference = end.getTime() - start.getTime();
                Window.alert("LOCAL\nStart: " + start.getTime() + "\nEnd: " + end.getTime() +
                        "\nTime in ms: " + difference);
            }
        });
    }

    private int testCount = 0;
    Date start;
    Date end;

    final AsyncCallback testCallback = new AsyncCallback() {

        public void onFailure(Throwable caught) {
            throw new UnsupportedOperationException("Not supported yet.");
        }

        public void onSuccess(Object result) {
            testCount++;
            if (testCount == 99) {
                end = new Date();
                Long difference = end.getTime() - start.getTime();
                Window.alert("REMOTE\nStart: " + start.getTime() + "\nEnd: " + end.getTime() +
                        "\nTime in ms: " + difference);
            }
        }
    };

}
